package com.xy.iot.netty.handle;

import cn.hutool.core.util.StrUtil;
import com.xy.iot.common.message.BaseMessage;
import com.xy.iot.netty.util.SendUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

/**
 * 类功能说明：自定义编码器，将数据编码后进行下发
 *        Encoder对应的就是ChannelOutboundHandler，消息对象转换为字节数组
 *              Netty本身未提供和解码一样的编码器，是因为场景不同，两者非对等的
 *                  1)MessageToByteEncoder
 *                          消息转为字节数组,调用write方法，会先判断当前编码器是否支持需要发送的消息类型，如果不支持，则透传；
 *                  2)MessageToMessageEncoder
 *                          用于从一种消息编码为另外一种消息（例如POJO到POJO）
 *        组合解码器和编码器，以此提供对于字节和消息都相同的操作
 *              优点：成对出现，编解码都是在一个类里面完成
 *              缺点：耦合在一起，拓展性不佳
 *         Codec:组合编解码
 *              1）ByteToMessageCodec
 *              2）MessageToMessageCodec
 *         decoder:解码
 *              1）ByteToMessageDecoder
 *              2）MessageToMessageDecoder
 *         encoder:编码
 *              1）ByteToMessageEncoder
 *              2）MessageToMessageEncoder
 *        讲解什么是TCP粘包拆包讲解
 *              1）TCP拆包: 一个完整的包可能会被TCP拆分为多个包进行发送
 *              2）TCP粘包: 把多个小的包封装成一个大的数据包发送, client发送的若干数据包 Server接收时粘成一包
 *        发送方和接收方都可能出现这个原因
 *              发送方的原因：TCP默认会使用Nagle算法
 *              接收方的原因: TCP接收到数据放置缓存中，应用程序从缓存中读取
 *              UDP: 是没有粘包和拆包的问题，有边界协议
 *        讲解TCP半包读写常见的解决办法
 *              发送方：可以关闭Nagle算法
 *              接受方: TCP是无界的数据流，并没有处理粘包现象的机制, 且协议本身无法避免粘包，半包读写的发生需要在应用层进行处理
 *                     应用层解决半包读写的办法
 *                         1）设置定长消息 (10字符)
 *                                  xdclass000xdclass000xdclass000xdclass000
 *                         2）设置消息的边界 ($$ 切割)
 *                                  sdfafwefqwefwe$$dsafadfadsfwqehidwuehfiw$$879329832r89qweew$$
 *                         3）使用带消息头的协议，消息头存储消息开始标识及消息的长度信息
 *                                 Header+Body
 *         Netty自带解决半包读写问题方案介绍
 *             DelimiterBasedFrameDecoder： 指定消息分隔符的解码器
 *                   优点：简单易用， delimiter参数可以指定任意字符， delimiter参数可以指定任意字符
 *                   maxLength：表示一行最大的长度，如果超过这个长度依然没有检测自定义分隔符，将会抛出TooLongFrameException
 *                  failFast：
 *                              如果为true，则超出maxLength后立即抛出TooLongFrameException，不进行继续解码
 *                              如果为false，则等到完整的消息被解码后，再抛出TooLongFrameException异常
 *                  stripDelimiter：解码后的消息是否去除掉分隔符
 *                  delimiters：分隔符，ByteBuf类型
 *                  lengthFieldOffset：长度字段的偏移量，即长度字段相对于整个消息的偏移量
 *             LineBasedFrameDecoder: 以换行符为结束标志的解码器
 *                   以换行符为结束标志的解码器 ,构造函数里面的数字表示最长遍历的帧数
 *             FixedLengthFrameDecoder：固定长度解码器
 *             LengthFieldBasedFrameDecoder：message = header+body, 基于长度解码的通用解码器
 *                   maxFrameLength 数据包的最大长度
 *                   lengthFieldOffset 长度字段的偏移位，长度字段开始的地方，意思是跳过指定长度个字节之后的才是消息体字段
 *                   lengthFieldLength 长度字段占的字节数, 帧数据长度的字段本身的长度
 *                   lengthAdjustment  一般 Header + Body，添加到长度字段的补偿值,如果为负数，开发人员认为这个 Header的长度字段是整个消息包的长度，则Netty应该减去对应的数字
 *                   initialBytesToStrip 从解码帧中第一次去除的字节数, 获取完一个完整的数据包之后，忽略前面的指定位数的长度字节，应用解码器拿到的就是不带长度域的数据包
 *                   failFast 是否快速失败
 *             StringDecoder：解码器将对象转成字符串
 */
@Slf4j
public class ByteClientMessageEncoder extends MessageToByteEncoder<BaseMessage> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, BaseMessage baseMessage, ByteBuf out) throws Exception {
        log.info("ByteClientMessageEncoder.encode(),返回数据信息：{}", StrUtil.str(baseMessage.toBytes(), Charset.defaultCharset()));
        //直接，调用发送数据，不进入下个处理器，如果想进入下一个处理器，则需要调用channelHandlerContext.fireChannelRead(baseMessage); 或者 channel.write(msg)
        out.clear().writeBytes(SendUtil.getByteBuf(baseMessage));
    }
}
